#include <fmt/format.h>

#include "net/ethernet.h"
#include "util/logger.h"

using namespace kawauso;
using namespace std;

#define	JUMBO_FRAME_MAX_SIZE 0x2600 // allow max jumbo frame 9.5 KB
#define DEFAULT_RX_DESC_NUM 1024
#define DEFAULT_TX_DESC_NUM 1024
#define MAX_PACKETS 32

rte_eth_conf defaultPortConfig = {
    .rxmode = {
        .mq_mode = rte_eth_rx_mq_mode::RTE_ETH_MQ_RX_RSS,
        .mtu = JUMBO_FRAME_MAX_SIZE - RTE_ETHER_HDR_LEN - RTE_ETHER_CRC_LEN
    },
    .txmode = {
        .mq_mode = RTE_ETH_MQ_TX_NONE,
        .offloads = RTE_ETH_TX_OFFLOAD_MULTI_SEGS
    },
    .rx_adv_conf = {
        .rss_conf = {
            .rss_hf = RTE_ETH_RSS_ETH
        }
    }
};

EthernetDevice::EthernetDevice(uint16_t portId, uint16_t queueNum,
                               rte_eth_dev_info &information)
    : rxConfig(information.default_rxconf)
    , txConfig(information.default_txconf)
    , portConfig(defaultPortConfig)
    , rxQueues(queueNum)
    , txQueues(queueNum)
    , rxDescNum(DEFAULT_RX_DESC_NUM)
    , txDescNum(DEFAULT_TX_DESC_NUM)
    , portId(portId)
{
    // adjust rx mtu, see eth_dev_validate_mtu()
    if (information.max_rx_pktlen <= information.max_mtu) {
        portConfig.rxmode.mtu = min(portConfig.rxmode.mtu, information.max_rx_pktlen - RTE_ETHER_HDR_LEN - RTE_ETHER_CRC_LEN);
    } else {
        portConfig.rxmode.mtu = min(portConfig.rxmode.mtu, (uint32_t)information.max_mtu);
    }

    int res = rte_eth_dev_adjust_nb_rx_tx_desc(portId, &rxDescNum, &txDescNum);
    if (res < 0) {
        throw std::runtime_error(fmt::format(
                "Failed to adjust number of RX/TX descriptors ({})", name));
    }

    this->queueNum = min({information.max_rx_queues, information.max_tx_queues, queueNum});
    this->name = rte_dev_name(information.device);
}

EthernetDevice::~EthernetDevice() {
    //
}

void EthernetDevice::init(std::vector<rte_mempool *> &packetBufferPools) {

    int res = rte_eth_dev_configure(portId, queueNum, queueNum, &portConfig);
    if (res < 0) {
        throw std::runtime_error(fmt::format(
                "Failed to configure device ({})", name));
    }

    for (uint16_t queueId = 0; queueId < queueNum; queueId++) {
        rxQueues[queueId] = new ReceiveQueue(queueId, *this, packetBufferPools[queueId]);
    }

    res = rte_eth_dev_start(portId);
    if (res < 0) {
        throw std::runtime_error(fmt::format(
                "Failed to start device ({})", name));
    }

    info("{} (port: {}) has started successfully!", name, portId);
}

ReceiveQueue& EthernetDevice::getRxQueue(uint16_t queueId) {
    return *rxQueues[queueId];
}

uint16_t EthernetDevice::getQueueCount() const {
    return queueNum;
}

ReceiveQueue::ReceiveQueue(uint16_t queueId, EthernetDevice &device,
                                   rte_mempool *packetBufferPool)
    : device(device), queueId(queueId)
{
    int res = rte_eth_rx_queue_setup(device.portId, queueId, device.rxDescNum,
                                     rte_eth_dev_socket_id(device.portId),
                                     &device.rxConfig,
                                     packetBufferPool);
    if (res < 0) {
        throw std::runtime_error(fmt::format(
                "Failed to setup RX queue ({})", device.name));
    }
}

std::vector<rte_mbuf *> ReceiveQueue::receivePackets() const {
    rte_mbuf *packets[MAX_PACKETS];
    uint16_t packetNum = rte_eth_rx_burst(device.portId, queueId, packets, MAX_PACKETS);
    return {packets, packets + packetNum};
}
